#!/usr/bin/env python2
# Main code for host side printer firmware
#
# Copyright (C) 2016-2024  Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
# 在主文件中文件前面的导入是最开始执行的操作,然后就是在导入过程中导入导入模块中的带入语句,
# 直到运行到最后一个模块中的非类语句后算完成最后一个模块的导入,
# 回到上一个模块中执行非类语句,以此类推直到完成回溯,之后进入main函数
import sys, os, gc, optparse, logging, time, collections, importlib
import util, reactor, queuelogger, msgproto
import gcode, configfile, pins, mcu, toolhead, webhooks

message_ready = "Printer is ready"

message_startup = """
Printer is not ready
The klippy host software is attempting to connect.  Please
retry in a few moments.
"""

message_restart = """
Once the underlying issue is corrected, use the "RESTART"
command to reload the config and restart the host software.
Printer is halted
"""
######################################################################
# 语法知识补充 - 类

# 类是面向对象编程中的重要概念，它是一种用户自定义的数据类型，用于封装数据和操作这些数据的方法。

# 1. 类的定义
# 在 Python 中，使用 class 关键字来定义一个类，类名通常采用大驼峰命名法。
# 如果类体暂时没有具体实现，可以使用 pass 语句占位。

# class MyClass:
#     pass  # 这个关键字的作用是占位，没有任何具体功能


# 2. 类的实例化
# 类定义好后，需要创建类的实例才能使用，这个过程称为实例化。

# 创建 MyClass 类的一个实例
# my_object = MyClass()
# print(f"创建的实例: {my_object}")
# 这个 f 是Python 的 f-string 实现的效果类似于printf("%s\r\n",str);

# 3. 类的属性
# 类的属性是与类或类的实例相关联的变量。
# 类属性是所有实例共享的属性，而实例属性是每个实例独有的属性。

# class Person:
#     # 类属性，所有 Person 实例共享
#     species = "Homo sapiens"

#     def __init__(self, name, age):
#         # 实例属性，每个 Person 实例都有自己的 name 和 age
#         self.name = name
#         self.age = age
# 在Python中, 类和实例化对象都有自己的 __dict__ 字典 self.name 实际上是向实例化对象的 __dict__ 字典中创建 名叫 name 的 Key 而 “= name” 这个赋值行为是
# self.__dict__["name"] = name

# 创建 Person 类的实例
# person1 = Person("Alice", 25)
# person2 = Person("Bob", 30)

# 访问类属性
# print(f"Person 类的类属性 species: {Person.species}")

# 访问实例属性
# print(f"{person1.name} 的年龄是 {person1.age}")
# print(f"{person2.name} 的年龄是 {person2.age}")


# 4. 类的方法
# 类的方法是定义在类中的函数，用于执行与类相关的操作。
# 实例方法是最常见的方法类型，它的第一个参数通常是 self，代表类的实例。

# class Rectangle:
#     def __init__(self, length, width):
#         self.length = length
#         self.width = width

#     # 实例方法，用于计算矩形的面积
#     def area(self):
#         return self.length * self.width


# 创建 Rectangle 类的实例
# rect = Rectangle(5, 3)

# 调用实例方法
# print(f"矩形的面积是: {rect.area()}")


# 5. 类方法和静态方法
# 类方法使用 @classmethod 装饰器定义，它的第一个参数通常是 cls，代表类本身。
# 静态方法使用 @staticmethod 装饰器定义，它不需要特定的第一个参数。

# class MathUtils:
#     @classmethod
#     def add(cls, a, b):
#         return a + b

#     @staticmethod
#     def multiply(a, b):
#         return a * b


# 调用类方法
# result_add = MathUtils.add(2, 3)
# print(f"类方法加法结果: {result_add}")

# 调用静态方法
# result_multiply = MathUtils.multiply(2, 3)
# print(f"静态方法乘法结果: {result_multiply}")


# 6. 继承
# 继承是面向对象编程的一个重要特性，它允许一个类继承另一个类的属性和方法。
# 被继承的类称为父类（基类），继承的类称为子类（派生类）。

# class Animal:
#     def __init__(self, name):
#         self.name = name

#     def speak(self):
#         print(f"{self.name} 发出声音")


# class Dog(Animal):
#     def speak(self):
#         print(f"{self.name} 汪汪叫")


# 创建 Dog 类的实例
# dog = Dog("Buddy")

# 调用子类重写的方法
# dog.speak()
# 整个调用过程可以概括为：创建 Dog 类的实例时，调用父类 Animal 的 __init__ 方法初始化实例属性；调用 speak 方法时，由于 Dog 类重写了该方法，会优先调用 Dog 类中的 speak 方法，从而输出 "Buddy 汪汪叫"。

# 7. 多态
# 多态是指不同的对象对同一消息做出不同的响应。
# 在 Python 中，多态通过继承和方法重写来实现。

# 定义一个基类 Shape，代表所有图形的抽象概念
# class Shape:
    # def draw(self):
        # 基类中的 draw 方法只是一个占位，不做具体绘制操作
        # print("绘制一个图形")

# 定义圆形类，继承自 Shape 类
# class Circle(Shape):
#     def __init__(self, radius):
        # 初始化圆形的半径
        # self.radius = radius

#     def draw(self):
        # 重写 draw 方法，实现圆形的绘制逻辑
        # print(f"绘制一个半径为 {self.radius} 的圆形")

# 定义矩形类，继承自 Shape 类
# class Rectangle(Shape):
#     def __init__(self, width, height):
        # 初始化矩形的宽度和高度
        # self.width = width
        # self.height = height

    # def draw(self):
        # 重写 draw 方法，实现矩形的绘制逻辑
        # print(f"绘制一个宽为 {self.width}，高为 {self.height} 的矩形")

# 定义一个多态函数，用于绘制任意形状的图形
# def draw_shape(shape):
    # 调用传入对象的 draw 方法
    # shape.draw()

# 创建不同图形的实例
# circle = Circle(5)
# rectangle = Rectangle(4, 6)

# 调用多态函数绘制不同的图形
# draw_shape(circle)
# draw_shape(rectangle)

######################################################################

class Printer:
    config_error = configfile.error
    command_error = gcode.CommandError
    def __init__(self, main_reactor, bglogger, start_args):
        self.bglogger = bglogger
        self.start_args = start_args
        self.reactor = main_reactor
        self.reactor.register_callback(self._connect)
        self.state_message = message_startup
        self.in_shutdown_state = False
        self.run_result = None
        self.event_handlers = {}
        self.objects = collections.OrderedDict()
        # Init printer components that must be setup prior to config
        for m in [gcode, webhooks]:
            m.add_early_printer_objects(self)
    def get_start_args(self):
        return self.start_args
    def get_reactor(self):
        return self.reactor
    def get_state_message(self):
        if self.state_message == message_ready:
            category = "ready"
        elif self.state_message == message_startup:
            category = "startup"
        elif self.in_shutdown_state:
            category = "shutdown"
        else:
            category = "error"
        return self.state_message, category
    def is_shutdown(self):
        return self.in_shutdown_state
    def _set_state(self, msg):
        if self.state_message in (message_ready, message_startup):
            self.state_message = msg
        if (msg != message_ready
            and self.start_args.get('debuginput') is not None):
            self.request_exit('error_exit')
    def update_error_msg(self, oldmsg, newmsg):
        if (self.state_message != oldmsg
            or self.state_message in (message_ready, message_startup)
            or newmsg in (message_ready, message_startup)):
            return
        self.state_message = newmsg
        logging.error(newmsg)
    def add_object(self, name, obj):
        if name in self.objects:
            raise self.config_error(
                "Printer object '%s' already created" % (name,))
        self.objects[name] = obj
    def lookup_object(self, name, default=configfile.sentinel):
        if name in self.objects:
            return self.objects[name]
        if default is configfile.sentinel:
            raise self.config_error("Unknown config object '%s'" % (name,))
        return default
    def lookup_objects(self, module=None):
        if module is None:
            return list(self.objects.items())
        prefix = module + ' '
        objs = [(n, self.objects[n])
                for n in self.objects if n.startswith(prefix)]
        if module in self.objects:
            return [(module, self.objects[module])] + objs
        return objs
    def load_object(self, config, section, default=configfile.sentinel):
        if section in self.objects:
            return self.objects[section]
        module_parts = section.split()
        module_name = module_parts[0]
        py_name = os.path.join(os.path.dirname(__file__),
                               'extras', module_name + '.py')
        py_dirname = os.path.join(os.path.dirname(__file__),
                                  'extras', module_name, '__init__.py')
        if not os.path.exists(py_name) and not os.path.exists(py_dirname):
            if default is not configfile.sentinel:
                return default
            raise self.config_error("Unable to load module '%s'" % (section,))
        mod = importlib.import_module('extras.' + module_name)
        init_func = 'load_config'
        if len(module_parts) > 1:
            init_func = 'load_config_prefix'
        init_func = getattr(mod, init_func, None)
        if init_func is None:
            if default is not configfile.sentinel:
                return default
            raise self.config_error("Unable to load module '%s'" % (section,))
        self.objects[section] = init_func(config.getsection(section))
        return self.objects[section]
    def _read_config(self):
        self.objects['configfile'] = pconfig = configfile.PrinterConfig(self)
        config = pconfig.read_main_config()
        if self.bglogger is not None:
            pconfig.log_config(config)
        # Create printer components
        for m in [pins, mcu]:
            m.add_printer_objects(config)
        for section_config in config.get_prefix_sections(''):
            self.load_object(config, section_config.get_name(), None)
        for m in [toolhead]:
            m.add_printer_objects(config)
        # Validate that there are no undefined parameters in the config file
        pconfig.check_unused_options(config)
    def _connect(self, eventtime):
        try:
            self._read_config()
            self.send_event("klippy:mcu_identify")
            for cb in self.event_handlers.get("klippy:connect", []):
                if self.state_message is not message_startup:
                    return
                cb()
        except (self.config_error, pins.error) as e:
            logging.exception("Config error")
            self._set_state("%s\n%s" % (str(e), message_restart))
            return
        except msgproto.error as e:
            msg = "Protocol error"
            logging.exception(msg)
            self._set_state(msg)
            self.send_event("klippy:notify_mcu_error", msg, {"error": str(e)})
            util.dump_mcu_build()
            return
        except mcu.error as e:
            msg = "MCU error during connect"
            logging.exception(msg)
            self._set_state(msg)
            self.send_event("klippy:notify_mcu_error", msg, {"error": str(e)})
            util.dump_mcu_build()
            return
        except Exception as e:
            logging.exception("Unhandled exception during connect")
            self._set_state("Internal error during connect: %s\n%s"
                            % (str(e), message_restart,))
            return
        try:
            self._set_state(message_ready)
            for cb in self.event_handlers.get("klippy:ready", []):
                if self.state_message is not message_ready:
                    return
                cb()
        except Exception as e:
            logging.exception("Unhandled exception during ready callback")
            self.invoke_shutdown("Internal error during ready callback: %s"
                                 % (str(e),))
    def run(self):
        systime = time.time()
        monotime = self.reactor.monotonic()
        logging.info("Start printer at %s (%.1f %.1f)",
                     time.asctime(time.localtime(systime)), systime, monotime)
        # Enter main reactor loop
        try:
            self.reactor.run()
        except:
            msg = "Unhandled exception during run"
            logging.exception(msg)
            # Exception from a reactor callback - try to shutdown
            try:
                self.reactor.register_callback((lambda e:
                                                self.invoke_shutdown(msg)))
                self.reactor.run()
            except:
                logging.exception("Repeat unhandled exception during run")
                # Another exception - try to exit
                self.run_result = "error_exit"
        # Check restart flags
        run_result = self.run_result
        try:
            if run_result == 'firmware_restart':
                self.send_event("klippy:firmware_restart")
            self.send_event("klippy:disconnect")
        except:
            logging.exception("Unhandled exception during post run")
        return run_result
    def set_rollover_info(self, name, info, log=True):
        if log:
            logging.info(info)
        if self.bglogger is not None:
            self.bglogger.set_rollover_info(name, info)
    def invoke_shutdown(self, msg, details={}):
        if self.in_shutdown_state:
            return
        logging.error("Transition to shutdown state: %s", msg)
        self.in_shutdown_state = True
        self._set_state(msg)
        for cb in self.event_handlers.get("klippy:shutdown", []):
            try:
                cb()
            except:
                logging.exception("Exception during shutdown handler")
        logging.info("Reactor garbage collection: %s",
                     self.reactor.get_gc_stats())
        self.send_event("klippy:notify_mcu_shutdown", msg, details)
    def invoke_async_shutdown(self, msg, details):
        self.reactor.register_async_callback(
            (lambda e: self.invoke_shutdown(msg, details)))
    def register_event_handler(self, event, callback):
        self.event_handlers.setdefault(event, []).append(callback)
    def send_event(self, event, *params):
        return [cb(*params) for cb in self.event_handlers.get(event, [])]
    def request_exit(self, result):
        if self.run_result is None:
            self.run_result = result
        self.reactor.end()


######################################################################
# Startup
######################################################################

def import_test():
    # Import all optional modules (used as a build test)
    dname = os.path.dirname(__file__)
    for mname in ['extras', 'kinematics']:
        for fname in os.listdir(os.path.join(dname, mname)):
            if fname.endswith('.py') and fname != '__init__.py':
                module_name = fname[:-3]
            else:
                iname = os.path.join(dname, mname, fname, '__init__.py')
                if not os.path.exists(iname):
                    continue
                module_name = fname
            importlib.import_module(mname + '.' + module_name)
    sys.exit(0)

###############################################################
# 字典知识点补充:
    # 字典的基本概念
        # 字典是 Python 中的一种数据结构，用于存储键值对。键必须是唯一且不可变的（如字符串、数字、元组），值可以是任意类型的数据。

    # 创建字典的不同方式
        # 创建一个空字典
            # 方式一：使用花括号
            # empty_dict = {}
            # print(f"通过花括号创建的空字典: {empty_dict}")
            # 方式二：使用 dict() 函数
            # another_empty_dict = dict()
            # print(f"通过 dict() 函数创建的空字典: {another_empty_dict}")

        # 创建一个包含键值对的字典
            # 方式一：直接使用花括号
            # person = {
                # "name": "Alice",
                # "age": 25,
                # "city": "New York"
            # }
            # print(f"通过花括号创建的包含键值对的字典: {person}")

            # 方式二：使用 dict() 函数和关键字参数
            # another_person = dict(name="Bob", age=30, city="Los Angeles")
            # print(f"通过 dict() 函数和关键字参数创建的字典: {another_person}")

            # 方式三：使用 dict() 函数和可迭代对象
            # data = [("name", "Charlie"), ("age", 35), ("city", "Chicago")]
            # third_person = dict(data)
            # print(f"通过 dict() 函数和可迭代对象创建的字典: {third_person}")

    # 访问字典中的值
        # 可以通过键来访问字典中的值
        # print(f"person 字典中 name 的值: {person['name']}")

        # 为了避免键不存在时引发 KeyError 异常，可以使用 get() 方法
        # print(f"person 字典中 occupation 的值（使用 get 方法）: {person.get('occupation')}")
        # print(f"person 字典中 occupation 的值（使用 get 方法并设置默认值）: {person.get('occupation', 'Unknown')}")

    # 修改字典中的值
        # 可以通过键来修改字典中的值
        # person["age"] = 26
        # print(f"修改后 person 字典中 age 的值: {person['age']}")

    # 添加新的键值对
        # 直接使用新的键来添加键值对
        # person["occupation"] = "Engineer"
        # print(f"添加新键值对后 person 字典: {person}")

    # 删除键值对
        # 方式一：使用 del 关键字
        # del person["city"]
        # print(f"使用 del 删除 city 键后 person 字典: {person}")

        # 方式二：使用 pop() 方法
        # removed_age = person.pop("age")
        # print(f"使用 pop 删除 age 键后 person 字典: {person}")
        # print(f"被删除的 age 键对应的值: {removed_age}")

        # 方式三：使用 popitem() 方法，删除并返回最后插入的键值对
        # last_item = person.popitem()
        # print(f"使用 popitem 删除最后一个键值对后 person 字典: {person}")
        # print(f"被删除的最后一个键值对: {last_item}")

    # 检查键是否存在
        # 可以使用 in 关键字来检查键是否存在于字典中
        # if "name" in person:
        #     print("键 'name' 存在于 person 字典中")
        # else:
        #     print("键 'name' 不存在于 person 字典中")

    # 字典的遍历
        # 遍历所有键
        # print("person 字典的所有键:")
        # for key in person.keys():
        #     print(key)

        # 遍历所有值
        # print("person 字典的所有值:")
        # for value in person.values():
        #     print(value)

        # 遍历所有键值对
        # print("person 字典的所有键值对:")
        # for key, value in person.items():
        #     print(f"{key}: {value}")

    # 字典的长度
        # 可以使用 len() 函数获取字典中键值对的数量
        # print(f"person 字典的长度: {len(person)}")
# 语法知识补充
    # Python 用4个空格控制缩进,同等级的缩进被认为是同一代码块。
###############################################################
def arg_dictionary(option, opt_str, value, parser):
# 输入 python script.py --dictionary=mcu1=/path/to/dictionary1
# option 访问opts.add_option创建的项
# opt_str 的值为 '--dictionary'
# value 为 "mcu1=/path/to/dictionary1"
# parser 访问 optparse.OptionParser 创建的对象,即 opts
    ######################################
    # Python赋值方法:
    # value = "example.txt"
    # key, fname = "dictionary", value
    # print(key)  # 输出: dictionary
    # print(fname)  # 输出: example.txt
    ######################################
    key, fname = "dictionary", value
    if '=' in value: # 判断 value中是否包含 '='
        # split('=', 1)方法将value按=分割成两部分，第一部分赋值给mcu_name，第二部分赋值给fname。同时，将key更新为"dictionary_"加上mcu_name。
        mcu_name, fname = value.split('=', 1)
        key = "dictionary_" + mcu_name
    # parser.values.dictionary就是 <opts.add_option("-d", "--dictionary", dest="dictionary", type="string">中的dest="dictionary"
    if parser.values.dictionary is None:
        parser.values.dictionary = {}
    parser.values.dictionary[key] = fname

def main():
    # %prog 这个是占位符功能类似 ("%s",__FILE__)
    usage = "%prog [options] <config file>"
    # 创建一个命令行参数解析器对象
    opts = optparse.OptionParser(usage)
    # i 和 --debuginput 是选项的短形式和长形式，用户可以在命令行中使用其中任意一个来指定该选项
    # dest="inputtty" 表示选项值将存储在options.inputtty中
    # default='/tmp/printer' 指定了该选项的默认值，如果用户在命令行中没有指定该选项,options.inputtty 将被设置为/tmp/printer
    # type="string" 指定该选项的值为字符串类型
    # action="callback" 表示当解析到该选项时，将调用指定的回调函数arg_dictionary进行处理
    opts.add_option("-i", "--debuginput", dest="debuginput",
                    help="read commands from file instead of from tty port")
    opts.add_option("-I", "--input-tty", dest="inputtty",
                    default='/tmp/printer',
                    help="input tty name (default is /tmp/printer)")
    opts.add_option("-a", "--api-server", dest="apiserver",
                    help="api server unix domain socket filename")
    opts.add_option("-l", "--logfile", dest="logfile",
                    help="write log to file instead of stderr")
    opts.add_option("-v", action="store_true", dest="verbose",
                    help="enable debug messages")
    opts.add_option("-o", "--debugoutput", dest="debugoutput",
                    help="write output to file instead of to serial port")
    opts.add_option("-d", "--dictionary", dest="dictionary", type="string",
                    action="callback", callback=arg_dictionary,
                    help="file to read for mcu protocol dictionary")
    opts.add_option("--import-test", action="store_true",
                    help="perform an import module test")
    # 解析控制台命令 options能访问指定的选项实体,例如命令行有 "-i"则 options 能访问 debuginput; args是参数列表
    options, args = opts.parse_args()
    if options.import_test:
        import_test()
    if len(args) != 1:
        opts.error("Incorrect number of arguments")

    # 这是创建字典
    start_args = {'config_file': args[0], 'apiserver': options.apiserver,
                  'start_reason': 'startup'}
    # 首先，将 debuglevel 初始化为 logging.INFO，这是一个日志记录的默认级别，表示普通信息级别的日志。
    debuglevel = logging.INFO

    # 接着，判断 options.verbose 是否为 True
    if options.verbose:
        # 如果 options.verbose 为 True，说明启用了详细调试模式，将 debuglevel 更新为 logging.DEBUG，即设置为调试级别日志，这样会输出更详细的调试信息。
        debuglevel = logging.DEBUG
    # 然后，判断 options.debuginput 是否为 True
    if options.debuginput:
        # 如果 options.debuginput 为 True，说明要从文件中读取命令而不是从 tty 端口读取。
        start_args['debuginput'] = options.debuginput  # 将 options.debuginput 的值赋给 start_args 字典中的 'debuginput' 键
        debuginput = open(options.debuginput, 'rb')    # 以二进制读模式打开 options.debuginput 指定的文件，并将文件对象赋值给 debuginput 变量
        start_args['gcode_fd'] = debuginput.fileno()   # 将打开的文件的文件描述符赋值给 start_args 字典中的 'gcode_fd' 键，用于后续的操作
    else:
        start_args['gcode_fd'] = util.create_pty(options.inputtty)  # 如果 options.debuginput 为 False，调用 util 模块中的 create_pty 函数创建一个伪终端，并将其赋值给 start_args 字典中的 'gcode_fd' 键
    # 接下来，判断 options.debugoutput 是否为 True
    if options.debugoutput:
        start_args['debugoutput'] = options.debugoutput  # 如果 options.debugoutput 为 True，将 options.debugoutput 的值赋给 start_args 字典中的 'debugoutput' 键
        start_args.update(options.dictionary)  # 使用 update 方法将 options.dictionary 字典中的键值对更新到 start_args 字典中
    # 初始化 bglogger 为 None
    bglogger = None
    # 再判断 options.logfile 是否为 True
    if options.logfile:
        start_args['log_file'] = options.logfile  # 如果 options.logfile 为 True，将 options.logfile 的值赋给 start_args 字典中的 'log_file' 键
        bglogger = queuelogger.setup_bg_logging(options.logfile, debuglevel)  # 调用 queuelogger 模块中的 setup_bg_logging 函数，设置后台日志记录，并传入 options.logfile 和 debuglevel 作为参数，将返回的日志记录器对象赋值给 bglogger
    else:
        logging.getLogger().setLevel(debuglevel)  # 如果 options.logfile 为 False，获取 Python 的根日志记录器（通过 getLogger() 方法），并将其日志级别设置为之前确定的 debuglevel，即根据是否有指定日志文件来设置合适的日志级别

    # 向日志中写信息
    #####################start###################
    # 跳过
    logging.info("Starting Klippy...")
    git_info = util.get_git_version()
    git_vers = git_info["version"]
    extra_files = [fname for code, fname in git_info["file_status"]
                   if (code in ('??', '!!') and fname.endswith('.py')
                       and (fname.startswith('klippy/kinematics/')
                            or fname.startswith('klippy/extras/')))]
    modified_files = [fname for code, fname in git_info["file_status"]
                      if code == 'M']
    extra_git_desc = ""
    if extra_files:
        if not git_vers.endswith('-dirty'):
            git_vers = git_vers + '-dirty'
        if len(extra_files) > 10:
            extra_files[10:] = ["(+%d files)" % (len(extra_files) - 10,)]
        extra_git_desc += "\nUntracked files: %s" % (', '.join(extra_files),)
    if modified_files:
        if len(modified_files) > 10:
            modified_files[10:] = ["(+%d files)" % (len(modified_files) - 10,)]
        extra_git_desc += "\nModified files: %s" % (', '.join(modified_files),)
    extra_git_desc += "\nBranch: %s" % (git_info["branch"])
    extra_git_desc += "\nRemote: %s" % (git_info["remote"])
    extra_git_desc += "\nTracked URL: %s" % (git_info["url"])
    start_args['software_version'] = git_vers
    start_args['cpu_info'] = util.get_cpu_info()
    if bglogger is not None:
        versions = "\n".join([
            "Args: %s" % (sys.argv,),
            "Git version: %s%s" % (repr(start_args['software_version']),
                                   extra_git_desc),
            "CPU: %s" % (start_args['cpu_info'],),
            "Python: %s" % (repr(sys.version),)])
        logging.info(versions)
    elif not options.debugoutput:
        logging.warning("No log file specified!"
                        " Severe timing issues may result!")
    gc.disable()
    ####################end####################
    # Start Printer() class
    while 1:
        if bglogger is not None:
            bglogger.clear_rollover_info()
            bglogger.set_rollover_info('versions', versions)
        gc.collect() # 调用 Python 的垃圾回收机制，手动触发垃圾回收，释放不再使用的内存。
        # 这里实例化类 Reactor 类在 reactor.py 文件中
        main_reactor = reactor.Reactor(gc_checking=True)
        
        printer = Printer(main_reactor, bglogger, start_args)# 这里实例化了类
        res = printer.run()
        if res in ['exit', 'error_exit']:
            break
        time.sleep(1.)
        main_reactor.finalize()
        main_reactor = printer = None
        logging.info("Restarting printer")
        start_args['start_reason'] = res

    if bglogger is not None:
        bglogger.stop()

    if res == 'error_exit':
        sys.exit(-1)

if __name__ == '__main__':
    main()
